# -*- coding: utf-8 -*-
# Utilities for encryption and signing based on PKCS#1 v1.5
# See http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
"""Utilities for encryption and signing based on PKCS#1 v1.5.
See http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes

NOTICE: This program is used for learning, there may have bugs
and potential security issues. Please DO NOT use this program
in production environment.

WARNING: Do not pass any Python traceback information to users
for it may leaks the type and the position of a failure when
it occurred.
"""

__author__ = 'Kainan Zhu'
__email__ = 'dlangu0393@gmail.com'
__copyright__ = 'Copyright 2013, Kainan Zhu'

import binascii
import struct


def bit_length(number):
    """
    Return the amount of bits needed to store the number
    :param number:
        An positive integer value. The number to be calculated.
    :return:
        An integer value.
    """

    assert isinstance(number, (int, long))
    assert number >= 0

    ending_map = {
        '0': 0, '1': 1, '2': 2, '3': 2,
        '4': 3, '5': 3, '6': 3, '7': 3,
        '8': 4, '9': 4, 'a': 4, 'b': 4,
        'c': 4, 'd': 4, 'e': 4, 'f': 4,
    }

    hex_number = '%x' % (number,)

    return (len(hex_number) - 1) * 4 + ending_map[hex_number[0]]


def byte_length(number):
    """
    Return the amount of bytes needed to store the number
    :param number:
        An positive integer value. The number to be calculated.
    :return:
        An integer value.
    """

    assert isinstance(number, (int, long))
    assert number >= 0

    quotient, remainder = divmod(bit_length(number), 8)
    if number == 0 or not remainder == 0:
        quotient += 1

    return quotient


def bytes_to_int(convert_bytes):
    """
    Convert a list of bytes or an 8-bit string to int
    :param convert_bytes:
        Bytes to be converted.
    :return:
        An integer.
    """
    return int(binascii.hexlify(convert_bytes), 16)


def int_to_bytes(number, block_size=None):
    """
    Convert a number to a string of bytes
    :param number:
        An integer value, the number to be converted.
    :param block_size:
        The number of bytes to output. If the length of encoded number
        is less than block_size, then the encoded string will be padded
        by zero.
    :return:
        A string of bytes.
    """

    assert isinstance(number, (int, long))
    assert number >= 0

    if number == 0:
        needed_bytes = 1
        new_bytes = ['\x00']
    else:
        needed_bytes = byte_length(number)
        new_bytes = []

    padding = ''
    if block_size:
        assert needed_bytes <= block_size, 'block_size is too small to hold encoded string'
        padding = (block_size - needed_bytes) * '\x00'

    while number > 0:
        new_bytes.insert(0, struct.pack('>B', number & 0xFF))
        number >>= 8

    new_bytes.insert(0, padding)

    return ''.join(new_bytes)
